<!DOCTYPE html>

<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>Incremental exploration of a large graph</title>
<style>
@import url(../style.css);

.background {
    stroke: gray;
    stroke-width: 1px;
    fill: white;
}
.node {
  stroke: #fff;
  stroke-width: 1.5px;
}

.link {
  stroke: #999;
  stroke-opacity: .8;
}

</style>
</head>
<body>
    <a href="../index.html">cola.js home</a>
<h1>Online Graph Exploration</h1>
<p>Red nodes are incomplete.  Click on them to expand their neighbours.</p>
<script src="../extern/d3.v3.js"></script>
<script src="../cola.min.js"></script>
<script>
    var width = 960,
        height = 500,
        imageScale = 0.25;

    var color = d3.scale.category20()

    var cola = cola.d3adaptor(d3)
        .linkDistance(60)
        .size([width, height]);

    var outer = d3.select("body").append("svg")
        .attr("width", width)
        .attr("height", height)
        .attr("pointer-events", "all");

    outer.append('rect')
        .attr('class', 'background')
        .attr('width', "100%")
        .attr('height', "100%")
        .call(d3.behavior.zoom().on("zoom", redraw));

    var vis = outer
        .append('g');

    var nodeMouseDown = false;

    function redraw() {
        if (nodeMouseDown) return;
        vis.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
    }

    var edgesLayer = vis.append("g");
    var nodesLayer = vis.append("g");

    var modelgraph, viewgraph = { nodes: [], links: [] };

    d3.json("graphdata/biological.json", function (error, g) {
        modelgraph = g;
        var startNode = getNode("79");
        addViewNode(startNode);
        refocus(startNode);
        update();
    });

    function refocus(focus) {
        modelgraph.links.forEach(function (l) {
            var u = modelgraph.nodes[l.source], v = modelgraph.nodes[l.target];
            if (u === focus && !inView(v)) addViewNode(v, focus);
            if (v === focus && !inView(u)) addViewNode(u, focus);
        });
        viewgraph.links = [];
        viewgraph.nodes.forEach(function (v) {v.colour = "blue"});
        modelgraph.links.forEach(function (l) {
            var u = modelgraph.nodes[l.source], v = modelgraph.nodes[l.target];
            if (inView(u) && inView(v)) viewgraph.links.push({ source: u, target: v });
            if (inView(u) && !inView(v)) u.colour = "red";
            if (!inView(u) && inView(v)) v.colour = "red";
        });
    }

    function inView(v) { return typeof v.viewgraphid !== 'undefined'; }

    function addViewNode(v, startpos) {
        v.viewgraphid = viewgraph.nodes.length;
        if (typeof startpos !== 'undefined') {
            v.x = startpos.x;
            v.y = startpos.y;
        }
        viewgraph.nodes.push(v);
    }

    function getNode(name) {
        var v, i = modelgraph.nodes.length;
        while (i--) if ((v = modelgraph.nodes[i]).name == name) return v;
        return null;
    }

    function click(node) {
        if (node.colour == "blue") return;
        var focus = getNode(node.name);
        refocus(focus);
        update();
    }

    function toggleImageZoom(img) {
        var scale = 1;
        d3.select(img).each(function (d) {
            if (Math.abs(img.width.baseVal.value - d.width) < 1) scale /= imageScale;
        });
        imageZoom(img, scale);
    }

    function imageZoom(img, scale) {
        d3.select(img)
            .transition()
            .attr("width", function (d) {
                return scale * d.width;
            })
            .attr("height", function (d) { return scale * d.height; });
    }

    function update() {
        cola.nodes(viewgraph.nodes)
            .links(viewgraph.links)
            .start();

        var link = edgesLayer.selectAll(".link")
            .data(viewgraph.links);

        link.enter().append("line")
            .attr("class", "link")
            .style("stroke-width", 2);

        link.exit().remove();

        var node = nodesLayer.selectAll(".node")
            .data(viewgraph.nodes, function (d) { return d.viewgraphid; });

        var enter = node.enter().append("g")
            .attr("class", function (d) { return "node" + ('image' in d ? ' imagenode' : '') })
            .on("mousedown", function () { nodeMouseDown = true; })
            .on("mouseup", function () { nodeMouseDown = false; })
            .on("touchmove", function () {
                d3.event.preventDefault()
            })
            .call(cola.drag);

        nodesLayer.selectAll(".imagenode")
            .attr("class", "node")
            .append("image")
            .attr("xlink:href", function (d) {
                var url = "graphdata/" + d['image'] + ".gif";
                var simg = this;
                var img = new Image();
                img.onload = function () {
                    d.width = this.width * imageScale;
                    d.height = this.height * imageScale;
                    simg.setAttribute("width", d.width);
                    simg.setAttribute("height", d.height);
                }
                return img.src = url;
            })
            .on("mouseover", function () { imageZoom(this, 1/imageScale) })
            .on("touchend", function () { toggleImageZoom(this) })
            .on("mouseout", function () { imageZoom(this, 1) });

        enter.append("circle")
            .attr("r", 7)
            .on("click", function (d) { click(d) })
            .on("touchend", function (d) { click(d) });

        node.style("fill", function (d) { return d.colour; })
            .append("title")
            .text(function (d) { return d.label; });

        cola.on("tick", function () {
            link.attr("x1", function (d) { return d.source.x; })
                .attr("y1", function (d) { return d.source.y; })
                .attr("x2", function (d) { return d.target.x; })
                .attr("y2", function (d) { return d.target.y; });

            node.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; });
        });
    }
</script>
Mouse wheel zooms, left-mouse-button drag on the background to pan.
<p>
    This example introduces the notion of a "model" graph and a "view" graph.  The model graph is too large to sensibly display in one go.
    Instead we begin with just a small neighbourhood showing in the view graph which is bound to SVG visuals following the usual
    D3 pattern.  The red nodes have neighbours in the model graph
    that are not shown in the view graph.  Clicking on a red node causes its invisible neighbours to be added to the view and
    we use D3's enter/exit selections to update the display as usual.
    </p><p>
    The stability of WebCoLa layout makes this type of dynamic graph layout much less chaotic than is possible with the D3 force layout.
</p>
    <script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-63535113-1', 'auto');
  ga('send', 'pageview');

    </script>
</body>
</html>
